package com.evil.common.oss.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.evil.common.core.enums.RCodeEnum;
import com.evil.common.core.exception.BusinessException;
import com.evil.common.file.constant.OssConstant;
import com.evil.common.file.entity.UploadedInfo;
import com.evil.common.file.enums.BusTypeEnum;
import com.evil.common.file.enums.FileTypeEnum;
import com.evil.common.oss.factory.FileStorageServiceFactory;
import com.evil.common.oss.props.AliyunOssProperty;
import com.evil.common.oss.props.LocalOssProperty;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
public class FileHandleService {

    private final AliyunOssProperty aliyunOssProperty;
    private final LocalOssProperty localOssProperty;

    private FileStorageService getFileService() {
        if (aliyunOssProperty.getEnabled()) {
            return FileStorageServiceFactory.build(aliyunOssProperty);
        } else if (localOssProperty.getEnabled()) {
            return FileStorageServiceFactory.build(localOssProperty);
        } else {
            throw new BusinessException(RCodeEnum.UPLOAD_ERROR, "上传失败，未配置OSS服务器");
        }
    }

    /**
     * 生成文件名
     *
     * @param originalFilename originalFilename
     * @return String
     */
    private String generateFileName(String originalFilename) {
        String extension = Objects.requireNonNull(FilenameUtils.getExtension(originalFilename)).toLowerCase();
        extension = StringUtils.isBlank(extension) ? "" : ("." + extension);
        return IdUtil.fastSimpleUUID() + extension;
    }

    /**
     * 生成文件路径
     *
     * @param enterpriseId 企业id
     * @param busTypeEnum  业务类型
     * @param fileName     fileName
     * @return String
     */
    private String generateFilePath(Long enterpriseId, BusTypeEnum busTypeEnum, String fileName) {
        String busTypeFolder = busTypeEnum.isPrivate() ? OssConstant.UPLOAD_FOLDER_PROTECT : OssConstant.UPLOAD_FOLDER_PUBLIC;
        String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
        return String.format("%s/%s/%s/%s/%s", OssConstant.UPLOAD_BASE_FOLDER, busTypeFolder, enterpriseId, date, fileName);
    }

    /**
     * 获取文件服务Host
     *
     * @return String
     */
    public String getFileServerHost() {
        FileStorageService fileStorageService = this.getFileService();
        return fileStorageService.getHost() + fileStorageService.getVisitPath();
    }

    /**
     * 上传文件
     *
     * @param multipartFile multipartFile
     * @param busTypeEnum   busTypeEnum
     * @param maxSize       maxSize
     * @return UploadedInfo
     */
    public UploadedInfo upload(MultipartFile multipartFile, Long enterpriseId, BusTypeEnum busTypeEnum, Long maxSize, String allowContentType, boolean isPreview) {
        if (!validateFile(multipartFile, maxSize, allowContentType)) {
            throw new BusinessException(RCodeEnum.ERROR_FILE, "校验文件失败");
        }
        // 上传文件
        String sourceFileName = multipartFile.getOriginalFilename();
        String fileName = generateFileName(sourceFileName);
        String extension = FileNameUtil.extName(sourceFileName);
        String filePath = generateFilePath(enterpriseId, busTypeEnum, fileName);
        String uploadedPath;
        String fileMD5;

        // 使用已开启的OSS，优先阿里云
        FileStorageService fileStorageService = this.getFileService();
        try {
            fileMD5 = DigestUtils.md5Hex(multipartFile.getInputStream());
            uploadedPath = fileStorageService.upload(multipartFile.getInputStream(), filePath, sourceFileName, multipartFile.getContentType(), isPreview);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(RCodeEnum.ERROR_FILE, "上传文件，IO异常：" + e.getMessage());
        }
        return uploadSuccess(fileStorageService, busTypeEnum, fileMD5, sourceFileName, fileName, extension, uploadedPath, multipartFile.getSize());
    }

    /**
     * 上传文件
     *
     * @param file        file
     * @param busTypeEnum busTypeEnum
     * @param maxSize     maxSize
     * @return UploadedInfo
     */
    public UploadedInfo upload(File file, Long enterpriseId, BusTypeEnum busTypeEnum, Long maxSize) {
        if (!validateFile(file, maxSize)) {
            throw new BusinessException(RCodeEnum.ERROR_FILE, "校验文件失败");
        }
        // 上传文件
        String sourceFileName = file.getName();
        String fileName = generateFileName(sourceFileName);
        String extension = FileNameUtil.extName(sourceFileName);
        String filePath = generateFilePath(enterpriseId, busTypeEnum, fileName);
        String mimeType = FileUtil.getMimeType(sourceFileName);
        String uploadedPath;
        String fileMD5;

        // 使用已开启的OSS，优先阿里云
        FileStorageService fileStorageService = this.getFileService();
        try {
            fileMD5 = DigestUtils.md5Hex(Files.newInputStream(file.toPath()));
            uploadedPath = fileStorageService.upload(Files.newInputStream(file.toPath()), filePath, sourceFileName, mimeType, false);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(RCodeEnum.ERROR_FILE, "上传文件，IO异常：" + e.getMessage());
        }
        return uploadSuccess(fileStorageService, busTypeEnum, fileMD5, sourceFileName, fileName, extension, uploadedPath, file.length());
    }

    /**
     * 获取下载链接
     *
     * @param filePath       文件地址
     * @param expirationTime expirationTime
     * @return String
     */
    public String getDownloadUrl(String filePath, String fileName, Date expirationTime) {
        FileStorageService fileStorageService = this.getFileService();
        URL url = fileStorageService.getDownloadUrl(filePath, fileName, expirationTime);
        return url.toString();
    }

    /**
     * 获取下载输入流
     *
     * @param filePath       文件地址
     * @param expirationTime expirationTime
     * @return InputStream
     */
    public InputStream getDownloadInputStream(String filePath, String fileName, Date expirationTime) {
        FileStorageService fileService = this.getFileService();
        URL url = fileService.getDownloadUrl(filePath, fileName, expirationTime);
        try {
            return url.openConnection().getInputStream();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(RCodeEnum.ERROR_FILE, "上传文件，IO异常：" + e.getMessage());
        }
    }

    /**
     * 校验上传文件
     *
     * @param file    file
     * @param maxSize maxSize
     * @return boolean
     */
    private boolean validateFile(File file, Long maxSize) {
        if (file == null) {
            throw new BusinessException(RCodeEnum.ERROR_FILE, "没有上传文件");
        }

        if (maxSize != null) {
            if (file.length() > maxSize) {
                String errorMsg = String.format("文件大小限制，最大值：role%s，当前值：%s ,文件名：%s",
                        maxSize, file.length(), file.getName());
                log.error(errorMsg);
                throw new BusinessException(RCodeEnum.ERROR_FILE, errorMsg);
            }
        }
        return true;
    }

    /**
     * 校验上传文件
     *
     * @param multipartFile multipartFile
     * @param maxSize       maxSize
     * @return boolean
     */
    private boolean validateFile(MultipartFile multipartFile, Long maxSize, String allowContentType) {
        if (multipartFile == null) {
            throw new BusinessException(RCodeEnum.ERROR_FILE, "没有上传文件");
        }

        if (maxSize != null) {
            if (multipartFile.getSize() > maxSize) {
                String errorMsg = String.format("文件大小限制，最大值：%s，当前值：%s ,文件名：%s",
                        maxSize, multipartFile.getSize(), multipartFile.getOriginalFilename());
                log.error(errorMsg);
                throw new BusinessException(RCodeEnum.ERROR_FILE, errorMsg);
            }
        }
        if (StringUtils.isNotBlank(allowContentType)) {
            String contentType = multipartFile.getContentType();
            if (!allowContentType.contains(Objects.requireNonNull(contentType))) {
                String errorMsg = String.format("上传的文件类型不支持，mime-type：%s，文件名：%s",
                        multipartFile.getContentType(), multipartFile.getOriginalFilename());
                log.error(errorMsg);
                throw new BusinessException(RCodeEnum.ERROR_FILE, errorMsg);
            }
        }
        return true;
    }

    private UploadedInfo uploadSuccess(FileStorageService fileStorageService, BusTypeEnum busTypeEnum, String fileMD5, String sourceFileName, String fileName, String extension, String uploadedPath, Long size) {
        if (StringUtils.isBlank(uploadedPath)) {
            throw new BusinessException(RCodeEnum.ERROR_FILE, "上传文件，返回文件路径失败");
        }
        UploadedInfo uploadedInfo = new UploadedInfo();
        uploadedInfo.setFileName(sourceFileName);
        uploadedInfo.setFilePath(uploadedPath);
        if (busTypeEnum.isPrivate()) {
            // 私有文件走服务器接口（备注：私密文件，库中url值无效，因为在返回文件信息时候，重新组装了有过期时间的加密参数链接）
            uploadedInfo.setUrl(fileStorageService.getHost() + "/" + uploadedPath);
        } else {
            // 公开文件走预览
            uploadedInfo.setUrl(fileStorageService.getFileUrl(uploadedPath));
        }
        uploadedInfo.setFileMD5(fileMD5);
        uploadedInfo.setExtension(extension);
        uploadedInfo.setFileType(FileTypeEnum.getIdByExtension(extension));
        uploadedInfo.setSize(size);
        uploadedInfo.setServiceProvider(fileStorageService.getServiceProvider());
        log.info("上传文件成功，data: {}", JSON.toJSONString(uploadedInfo));
        return uploadedInfo;
    }
}
